#include "CCompiler.h"

#include <QFile>
#include <QTextStream>
#include <QIODevice>
#include <QStringList>
#include <QDebug>
#include <iostream>

#include "../core/CCoreException.h"
#include "../core/CGlobal.h"
#include "../core/COperationCodes.h"
#include "../core/CVMDataTypes.h"

CCompiler::CCompiler(QString path)
{
    this->path = path;

    parsers[OPERATION_CODE_PUSH] = &CCompiler::push;
    parsers[OPERATION_CODE_POP] = &CCompiler::pop;
    parsers[OPERATION_CODE_BIND] = &CCompiler::bind;
    parsers[OPERATION_CODE_GET_VAR] = &CCompiler::getVar;

    parsers[OPERATION_CODE_IF] = &CCompiler::if_;
    parsers[OPERATION_CODE_GO_TO] = &CCompiler::goTo;
    parsers[OPERATION_CODE_FUNC] = &CCompiler::func;
    parsers[OPERATION_CODE_CALL] = &CCompiler::invoke;
    parsers[OPERATION_CODE_RET] = &CCompiler::ret;
    parsers[OPERATION_CODE_END] = &CCompiler::end;

    parsers[OPERATION_CODE_OR] = &CCompiler::or_;
    parsers[OPERATION_CODE_AND] = &CCompiler::and_;
    parsers[OPERATION_CODE_NOT] = &CCompiler::not_;

    parsers[OPERATION_CODE_EQ] = &CCompiler::eq;
    parsers[OPERATION_CODE_NEQ] = &CCompiler::neq;
    parsers[OPERATION_CODE_GTE] = &CCompiler::gte;
    parsers[OPERATION_CODE_GT] = &CCompiler::gt;
    parsers[OPERATION_CODE_LTE] = &CCompiler::lte;
    parsers[OPERATION_CODE_LT] = &CCompiler::lt;

    parsers[OPERATION_CODE_ADD] = &CCompiler::add;
    parsers[OPERATION_CODE_SUB] = &CCompiler::sub;
    parsers[OPERATION_CODE_MUL] = &CCompiler::mul;
    parsers[OPERATION_CODE_DIV] = &CCompiler::div;
}

CByteArray CCompiler::compile()
{
    QFile fl(path);
    fl.open(QIODevice::ReadOnly | QIODevice::Text);
    QTextStream txtStrm(&fl);

    line = txtStrm.readLine();
    while(!line.isNull())
    {
        parse(line);
        line = txtStrm.readLine();
        lineNumber++;
    }

    fl.close();

    return byteArray;
}

void CCompiler::parse(QString string)
{
    if (string.isEmpty() || string.startsWith(OPERATION_CODE_COMMENT)) return;

    QStringList parts = string.split(" ");
    if (parsers.contains(parts[0]))
    {
        auto prsr = parsers[parts[0]];
        (this->*prsr)(parts);
    }
    else throw CCoreException(CCoreException::COMPILER_EXCEPTION_WRONG_OPERATION, QString("<%1> in <%2>:%3").arg(parts[0], line, QString::number(lineNumber)));
}

void CCompiler::push(QStringList& parts)
{
    if (parts.size() < 2)
        throw CCoreException(CCoreException::COMPILER_EXCEPTION_MISSING_OPERAND, QString("in <%1>:%2").arg(line, QString::number(lineNumber)));

    QString& oprnd = parts[1];
    if (oprnd.size() >= 2 && oprnd[0] == '"' && oprnd[oprnd.size() - 1] == '"')
    {
        QString str = oprnd.mid(1, oprnd.size() - 2);
        pushString(str);
    }
    else if (oprnd.size() >= 2 && oprnd[oprnd.size() - 1] == 'n')
    {
        QString number = oprnd.mid(0, oprnd.size() - 1);
        bool ok = true;
        double vl = number.toDouble(&ok);
        if (!ok) throw CCoreException(CCoreException::COMPILER_EXCEPTION_WRONG_OPERAND, QString("<%1> in <%2>:%3").arg(oprnd, line, QString::number(lineNumber)));
        else pushTypeNumber(vl);
    }
    else if (oprnd == "null") pushNull();
    else if (oprnd == "false") pushFalse();
    else if (oprnd == "true") pushTrue();
    else
    {
        bool ok = true;
        int vl = oprnd.toInt(&ok);
        if (!ok) throw CCoreException(CCoreException::COMPILER_EXCEPTION_WRONG_OPERAND, QString("<%1> in <%2>:%3").arg(oprnd, line, QString::number(lineNumber)));
        else pushInt(vl);
    }
}

void CCompiler::pop(QStringList&)
{
    byteArray.append((char)COperationCodes::POP);
}

void CCompiler::bind(QStringList&)
{
    byteArray.append((char)COperationCodes::BIND);
}

void CCompiler::getVar(QStringList&)
{
    byteArray.append((char)COperationCodes::GET_VAR);
}

void CCompiler::if_(QStringList&)
{
    byteArray.append((char)COperationCodes::IF);
}

void CCompiler::goTo(QStringList&)
{
    byteArray.append((char)COperationCodes::GO_TO);
}

void CCompiler::func(QStringList&)
{
    byteArray.append((char)COperationCodes::FUNC);
}

void CCompiler::invoke(QStringList&)
{
    byteArray.append((char)COperationCodes::CALL);
}

void CCompiler::ret(QStringList&)
{
    byteArray.append((char)COperationCodes::RET);
}

void CCompiler::end(QStringList&)
{
    byteArray.append((char)COperationCodes::END);
}

void CCompiler::or_(QStringList&)
{
    byteArray.append((char)COperationCodes::OR);
}

void CCompiler::and_(QStringList&)
{
    byteArray.append((char)COperationCodes::AND);
}

void CCompiler::not_(QStringList&)
{
    byteArray.append((char)COperationCodes::NOT);
}

void CCompiler::eq(QStringList&)
{
    byteArray.append((char)COperationCodes::EQ);
}

void CCompiler::neq(QStringList&)
{
    byteArray.append((char)COperationCodes::NEQ);
}

void CCompiler::gte(QStringList&)
{
    byteArray.append((char)COperationCodes::GTE);
}

void CCompiler::gt(QStringList&)
{
    byteArray.append((char)COperationCodes::GT);
}

void CCompiler::lte(QStringList&)
{
    byteArray.append((char)COperationCodes::LTE);
}

void CCompiler::lt(QStringList&)
{
    byteArray.append((char)COperationCodes::LT);
}

void CCompiler::add(QStringList&)
{
    byteArray.append((char)COperationCodes::ADD);
}

void CCompiler::sub(QStringList&)
{
    byteArray.append((char)COperationCodes::SUB);
}

void CCompiler::mul(QStringList&)
{
    byteArray.append((char)COperationCodes::MUL);
}

void CCompiler::div(QStringList&)
{
    byteArray.append((char)COperationCodes::DIV);
}

void CCompiler::pushInt(int value)
{
    byteArray.append((char)COperationCodes::PUSH);
    byteArray.addInt(sizeof(int));
    byteArray.addInt(value);
}

void CCompiler::pushTypeNumber(double value)
{
    byteArray.append((char)COperationCodes::PUSH);
    byteArray.addInt(sizeof(double) + 1);
    byteArray.append((char)CVMDataTypes::NUMBER);
    byteArray.addDouble(value);
}

void CCompiler::pushString(QString& value)
{
    QByteArray btAr = value.toUtf8();
    char* bts = btAr.data();

    byteArray.append((char)COperationCodes::PUSH);
    byteArray.addInt(btAr.size() + 1);
    byteArray.append(bts, btAr.size() + 1);

    delete[] bts;
}

void CCompiler::pushNull()
{
    byteArray.append((char)COperationCodes::PUSH);
    byteArray.addInt(sizeof((char)CVMDataTypes::NULL_));
    byteArray.append((char)CVMDataTypes::NULL_);
}

void CCompiler::pushFalse()
{
    byteArray.append((char)COperationCodes::PUSH);
    byteArray.addInt(sizeof((char)CVMDataTypes::FALSE));
    byteArray.append((char)CVMDataTypes::FALSE);
}

void CCompiler::pushTrue()
{
    byteArray.append((char)COperationCodes::PUSH);
    byteArray.addInt(sizeof((char)CVMDataTypes::TRUE));
    byteArray.append((char)CVMDataTypes::TRUE);
}
